/*
 * Copyright (c) 2010, Christian Lerche
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Author: Christian Lerche <christian.lerche@uni-rostock.de>
 *
 */

#include <stdio.h>
#include "common.h"

/* NOTE:
 * parsing functions has always an end-pointer to
 * avoid overflows but sometimes we need to check
 * a character after a character and to avoid fragmenting
 * the code to much with check functions we assume that the
 * buffer is TODO: 10 bytes longer than the p_end pointer
 *
 * buffer: xxxxxxxxx
 *         ^p       ^p_end
 * p_end points to the first address that doesn't belong to the buffer!*/
/* --------------const definitions---------------*/
const char whitespaces_and_colon[] = { ' ', '\n', '\r', '\t', ':' };

static void reverse_str(char s[]);

/*------------- common functions ---------------- */
/*itoa and reverse_str copied from http://en.wikipedia.org/wiki/Itoa */
void itoa2(int n, char s[]) {
    int i, sign;

    if ((sign = n) < 0) /* record sign */
        n = -n; /* make n positive */
    i = 0;
    do { /* generate digits in reverse order */
        s[i++] = n % 10 + '0'; /* get next digit */
    } while ((n /= 10) > 0); /* delete it */
    if (sign < 0)
        s[i++] = '-';
    s[i] = '\0';
    reverse_str(s);
}

/* TODO: reverse_str as inline */
static void reverse_str(char s[]) {
    int c, i, j;
    for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

/*---------------character based parsing functions -----------------*/

int find_next_c(char* p, char* p_end, char c, char** p_new) {
    for (; p < p_end; p++) {
        if (*p == c) {
            *p_new = p;
            return 0;
        }
    }
    return -1;
}

int find_next_c_arr(char* p, char* p_end, const char* c, int c_len, char** p_new) {
    int i;
    for (; p < p_end; p++) {
        for (i = 0; i < c_len; i++) {
            if (*p == c[i]) {
                *p_new = p;
                return 0;
            }
        }
    }
    return -1;
}

int ignore_c(char* p, char* p_end, char c, char** p_new) {
    while (*p == c) {
        p++;
        if (p >= p_end)
            return -1;
    }
    *p_new = p;
    return 0;
}

int ignore_c_arr(char* p, char* p_end, const char* c, int c_len, char** p_new) {
    int i, ignore = 1;
    while (ignore) {
        ignore = 0;
        for (i = 0; i < c_len; i++) {
            if (*p == c[i]) {
                p++;
                ignore = 1;
                break;
            }
        }
        if (p >= p_end)
            return -1;
    }
    *p_new = p;
    return 0;
}

int remove_whitesp_beg_end(char** buf, int *buf_len) {
    /* begin */
    char *p = *buf;
    char *p_last = p + *buf_len - 1;
    while ((*p == ' ') || (*p == '\t') || (*p == '\r') || (*p == '\n')) {
        p++;
        if (p == p_last) {
            return -1;
        }
    }
    *buf = p;
    /*end*/
    while ((*p_last == ' ') || (*p_last == '\t') || (*p_last == '\r') || (*p_last == '\n')) {
        p_last--;
        if (p == p_last) {
            return -1;
        }
    }
    *buf_len = p_last - p + 1;
    return 0;
}

/* ------------- xml based parsing functions --------------- */
int xml_next_tag(char* p, char* p_end, struct xml_tag_s* xml_tag, uint8_t* tag_type, uint8_t error_check, char** p_new) {
    int ignore = 1;
    char c_cmp;
    char* p_next;

    *tag_type = XML_TAG_TYPE_START;

    /* find next valid tag (ignore <? and <!)*/
    while (ignore) {
        if (find_next_c(p, p_end, '<', &p)) {
            /* couldn't find '<' */
            return -1;
        }
        c_cmp = *(p + 1);
        if (!(c_cmp == '?' || c_cmp == '!')) {
            ignore = 0;
        } else {
            p++; //to find the next (not the same) '<'
        }
    }
    /* p points to the '<' char of the current tag */
    p++; //go in

    /* check if this is a close tag e.g. </abc> */
    if (*p == '/') {
        if (IS_SET_FLAG(error_check, XML_NEXT_TAG_ERR_IF_CLOSE))
            return -1;
        *tag_type = XML_TAG_TYPE_END;
        p++;
    }

    /* fill structure or ignore */
    if (xml_tag) {
        /* clear result structure */
        memset(xml_tag, 0, sizeof(struct xml_tag_s));

        if (ignore_c_arr(p, p_end, whitespaces, whitespaces_len, &p))
            return -1; //ignore leading whitespace TODO: ignore also '\n' etc.
        if (find_next_c_arr(p, p_end, whitespaces_and_colon, whitespaces_and_colon_len, &p_next))
            return -1;
        if (*p_next == ':') {
            /*tag with namespace (e.g. <ns:abc>)*/
            xml_tag->namespace = p;
            xml_tag->tag_name = p_next + 1;
        } else {
            /* without namespace*/
            xml_tag->tag_name = p;
        }
        p = p_next;
    }
    /* goto end of tag*/
    if (find_next_c(p, p_end, '>', &p))
        return -1; /* couldn't find '>' */

    /* check empty tag */
    if (*(p - 1) == '/') {
        /* empty tag*/
        if (IS_SET_FLAG(error_check, XML_NEXT_TAG_ERR_IF_EMPTY)) {
            return -1;
        }
        *tag_type = XML_TAG_TYPE_EMPTY;
    }
    *p_new = p + 1;

    /* fill xml_tag with tag_type*/
    if (xml_tag) {
        xml_tag->tag_type = *tag_type;
    }

    return 0;
}

int xml_skip_tag_content(char* p, char* p_end, char** p_new) {
    int depth = 1; //tree depth
    uint8_t tag_type;
    while (depth != 0) {
        /* get next xml tag*/
        if (xml_next_tag(p, p_end, NULL, &tag_type, 0, &p)) {
            return -1;
        }
        switch (tag_type) {
        case XML_TAG_TYPE_START:
            depth++;
            break;
        case XML_TAG_TYPE_END:
            depth--;
        }
    }
    *p_new = p;
    return 0;
}

char* xml_cont_as_str(char* p, char* msg_end) {
    char* p_end;
    int len;
    if (find_next_c(p, msg_end, '<', &p_end))
        return NULL;
    /*content is between p and p_end */
    len = p_end - p;
    remove_whitesp_beg_end(&p, &len);
    if (len == 0)
        return NULL;
    *(p + len) = '\0';
    return p;
}

/*----------------------------------STACK implementation-----------------*/

void stack_init(struct stack_s *stack, char* mem, int len) {
    stack->mem = mem;
    stack->end = mem + len;
    stack->top = mem;
}
void stack_push(struct stack_s *stack, int len) {
    stack->top = stack->top - len;
}
char* stack_pop(struct stack_s *stack, int len) {
    char* top = stack->top;
    stack->top = top + len;
    if (stack->top > stack->end) {
        /* overflow */
        stack->top = top;
        return NULL;
    }
    return top;
}
char* stack_memdup(struct stack_s *stack, char* buf, int len) {
    char* top = stack->top;
    (stack->top = top + len);
    if (stack->top > stack->end) {
        /* overflow */
        stack->top = top;
        return NULL;
    }
    memcpy(top, buf, len);
    return top;
}

